#!/bin/bash
###############################################################################
#       脚本名称: log_rotation.sh
#       功能描述: 日志轮转增强模块,实现自动归档、压缩和清理
#       作者: 林再来
#       日期: 2025-10-14
#       版本: 1.0
#       功能:
#           1、按大小轮转(增强)
#           2、按时间轮转(增强)
#           3、自动归档到指定目录
#           4、压缩归档文件(gzip/bzip2)
#           5、自动清理过期日志
#           6、磁盘空间监控
#       依赖(插件/命令):
#           gzip/bzip2(压缩), du(磁盘使用统计)
###############################################################################

# 防止重复加载
[[ -n "${_LOG_ROTATION_LOADED:-}" ]] && return 0
readonly _LOG_ROTATION_LOADED=1

#-----------------------------------------------------------------------------
# 获取项目根目录
#-----------------------------------------------------------------------------
_LOG_ROTATION_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${_LOG_ROTATION_DIR}/../../.." && pwd)"

#-----------------------------------------------------------------------------
# 全局变量
#-----------------------------------------------------------------------------
# 轮转配置
declare -gA _LOG_ROTATION_CONFIG
# 归档目录（参考config模块的做法，先检查是否已定义）
if [[ -z "${DEFAULT_ARCHIVE_DIR:-}" ]]; then
    readonly DEFAULT_ARCHIVE_DIR="${PROJECT_ROOT}/logs/archive"
fi

#-----------------------------------------------------------------------------
# 默认配置（参考config模块的做法，先检查是否已定义）
#-----------------------------------------------------------------------------
if [[ -z "${DEFAULT_MAX_SIZE:-}" ]]; then
    readonly DEFAULT_MAX_SIZE="100M"
fi
if [[ -z "${DEFAULT_MAX_FILES:-}" ]]; then
    readonly DEFAULT_MAX_FILES=10
fi
if [[ -z "${DEFAULT_MAX_AGE_DAYS:-}" ]]; then
    readonly DEFAULT_MAX_AGE_DAYS=30
fi
if [[ -z "${DEFAULT_COMPRESS_METHOD:-}" ]]; then
    readonly DEFAULT_COMPRESS_METHOD="gzip"
fi
if [[ -z "${DEFAULT_COMPRESS_ENABLED:-}" ]]; then
    readonly DEFAULT_COMPRESS_ENABLED="true"
fi

#-----------------------------------------------------------------------------
# 函数: 初始化轮转配置
# 参数: $1 - 日志文件路径
#       $2 - 策略(size/time)
#       $3 - 最大大小或时间间隔
#       $4 - 最大文件数(可选)
#       $5 - 是否压缩(可选)
# 返回: 0-成功
#-----------------------------------------------------------------------------
function _log_rotation_init() {
    local log_file="$1"
    local strategy="${2:-size}"
    local max_size="${3:-$DEFAULT_MAX_SIZE}"
    local max_files="${4:-$DEFAULT_MAX_FILES}"
    local compress="${5:-$DEFAULT_COMPRESS_ENABLED}"
    
    _LOG_ROTATION_CONFIG["${log_file}_strategy"]="$strategy"
    _LOG_ROTATION_CONFIG["${log_file}_max_size"]="$max_size"
    _LOG_ROTATION_CONFIG["${log_file}_max_files"]="$max_files"
    _LOG_ROTATION_CONFIG["${log_file}_compress"]="$compress"
    
    # 创建归档目录
    local archive_dir=$(dirname "$log_file")/archive
    mkdir -p "$archive_dir"
    _LOG_ROTATION_CONFIG["${log_file}_archive_dir"]="$archive_dir"
    
    return 0
}

#-----------------------------------------------------------------------------
# 函数: 转换大小单位为字节
# 参数: $1 - 大小字符串(如: 10M, 1G, 100K)
# 返回: 输出字节数
#-----------------------------------------------------------------------------
function _parse_size() {
    local size_str="$1"
    local number="${size_str%[A-Za-z]*}"
    local unit="${size_str#$number}"
    
    case "${unit^^}" in
        K|KB) echo $((number * 1024)) ;;
        M|MB) echo $((number * 1024 * 1024)) ;;
        G|GB) echo $((number * 1024 * 1024 * 1024)) ;;
        *) echo "$number" ;;
    esac
}

#-----------------------------------------------------------------------------
# 函数: 检查是否需要轮转(按大小)
# 参数: $1 - 日志文件路径
# 返回: 0-需要轮转, 1-不需要
#-----------------------------------------------------------------------------
function _should_rotate_by_size() {
    local log_file="$1"
    
    if [[ ! -f "$log_file" ]]; then
        return 1
    fi
    
    local max_size="${_LOG_ROTATION_CONFIG[${log_file}_max_size]}"
    local max_bytes=$(_parse_size "$max_size")
    local current_size=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null || echo 0)
    
    [[ $current_size -ge $max_bytes ]]
}

#-----------------------------------------------------------------------------
# 函数: 执行日志轮转
# 参数: $1 - 日志文件路径
# 返回: 0-成功, 1-失败
#-----------------------------------------------------------------------------
function _log_rotation_rotate() {
    local log_file="$1"
    
    if [[ ! -f "$log_file" ]]; then
        return 0
    fi
    
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local archive_dir="${_LOG_ROTATION_CONFIG[${log_file}_archive_dir]}"
    local compress="${_LOG_ROTATION_CONFIG[${log_file}_compress]}"
    local log_basename=$(basename "$log_file")
    
    # 确保归档目录存在
    if [[ ! -d "$archive_dir" ]]; then
        mkdir -p "$archive_dir" || {
            echo "错误: 无法创建归档目录: $archive_dir" >&2
            return 1
        }
    fi
    
    # 生成归档文件名
    local archive_file="${archive_dir}/${log_basename}.${timestamp}"
    
    # 移动日志文件到归档目录
    mv "$log_file" "$archive_file" || {
        echo "错误: 无法移动日志文件到归档目录" >&2
        return 1
    }
    
    # 创建新的日志文件
    touch "$log_file"
    
    # 压缩归档文件
    if [[ "$compress" == "true" ]]; then
        _log_rotation_compress "$archive_file" &
    fi
    
    # 清理旧文件
    _log_rotation_cleanup "$log_file" &
    
    return 0
}

#-----------------------------------------------------------------------------
# 函数: 压缩归档文件
# 参数: $1 - 归档文件路径
# 返回: 0-成功
#-----------------------------------------------------------------------------
function _log_rotation_compress() {
    local archive_file="$1"
    local compress_method="${2:-gzip}"
    
    if [[ ! -f "$archive_file" ]]; then
        return 1
    fi
    
    case "$compress_method" in
        gzip)
            gzip -f "$archive_file" 2>/dev/null || {
                echo "警告: gzip压缩失败: $archive_file" >&2
            }
            ;;
        bzip2)
            bzip2 -f "$archive_file" 2>/dev/null || {
                echo "警告: bzip2压缩失败: $archive_file" >&2
            }
            ;;
        *)
            echo "警告: 不支持的压缩方法: $compress_method" >&2
            ;;
    esac
    
    return 0
}

#-----------------------------------------------------------------------------
# 函数: 清理过期日志
# 参数: $1 - 日志文件路径
# 返回: 0-成功
#-----------------------------------------------------------------------------
function _log_rotation_cleanup() {
    local log_file="$1"
    local archive_dir="${_LOG_ROTATION_CONFIG[${log_file}_archive_dir]}"
    local max_files="${_LOG_ROTATION_CONFIG[${log_file}_max_files]}"
    local log_basename=$(basename "$log_file")
    
    # 获取所有归档文件(包括压缩文件)
    local archive_files=($(ls -t "$archive_dir"/${log_basename}.* 2>/dev/null))
    local file_count=${#archive_files[@]}
    
    # 如果超过最大文件数,删除最老的
    if [[ $file_count -gt $max_files ]]; then
        local files_to_delete=$((file_count - max_files))
        for ((i=max_files; i<file_count; i++)); do
            rm -f "${archive_files[$i]}"
        done
    fi
    
    return 0
}

#-----------------------------------------------------------------------------
# 函数: 按时间清理日志
# 参数: $1 - 日志文件路径或归档目录
#       $2 - 最大保留天数
# 返回: 0-成功
#-----------------------------------------------------------------------------
function _log_rotation_cleanup_by_age() {
    local input_path="$1"
    local max_age_days="${2:-$DEFAULT_MAX_AGE_DAYS}"
    
    # 判断输入是归档目录还是日志文件
    local archive_dir=""
    if [[ -d "$input_path" ]]; then
        # 直接是目录
        archive_dir="$input_path"
    else
        # 是日志文件，从配置获取归档目录
        archive_dir="${_LOG_ROTATION_CONFIG[${input_path}_archive_dir]}"
    fi
    
    if [[ ! -d "$archive_dir" ]]; then
        return 0
    fi
    
    # 删除超过指定天数的文件
    find "$archive_dir" -type f -mtime +"$max_age_days" -delete 2>/dev/null
    
    return 0
}

#-----------------------------------------------------------------------------
# 函数: 检查磁盘空间
# 参数: $1 - 目录路径
#       $2 - 最小可用空间(如: 1G)
# 返回: 0-空间充足, 1-空间不足
#-----------------------------------------------------------------------------
function _log_rotation_check_disk_space() {
    local directory="$1"
    local min_space="${2:-1G}"
    
    local min_bytes=$(_parse_size "$min_space")
    local available_bytes=$(df -k "$directory" | awk 'NR==2 {print $4}')
    available_bytes=$((available_bytes * 1024))
    
    if [[ $available_bytes -lt $min_bytes ]]; then
        echo "警告: 磁盘空间不足! 可用: $((available_bytes / 1024 / 1024))MB, 需要: $((min_bytes / 1024 / 1024))MB" >&2
        return 1
    fi
    
    return 0
}

#-----------------------------------------------------------------------------
# 函数: 获取日志文件统计信息
# 参数: $1 - 日志文件路径
# 返回: 输出统计信息
#-----------------------------------------------------------------------------
function _log_rotation_stats() {
    local log_file="$1"
    local archive_dir="${_LOG_ROTATION_CONFIG[${log_file}_archive_dir]}"
    
    if [[ ! -f "$log_file" ]]; then
        echo "日志文件不存在: $log_file"
        return 1
    fi
    
    local current_size=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null || echo 0)
    local max_size="${_LOG_ROTATION_CONFIG[${log_file}_max_size]}"
    local strategy="${_LOG_ROTATION_CONFIG[${log_file}_strategy]}"
    local max_files="${_LOG_ROTATION_CONFIG[${log_file}_max_files]}"
    
    local archive_count=0
    local archive_total_size=0
    if [[ -d "$archive_dir" ]]; then
        archive_count=$(ls -1 "$archive_dir" 2>/dev/null | wc -l)
        archive_total_size=$(du -sb "$archive_dir" 2>/dev/null | awk '{print $1}')
    fi
    
    echo "日志轮转统计: $log_file"
    echo "  当前大小: $((current_size / 1024))KB"
    echo "  最大大小: $max_size"
    echo "  轮转策略: $strategy"
    echo "  最大文件数: $max_files"
    echo "  归档目录: $archive_dir"
    echo "  归档文件数: $archive_count"
    echo "  归档总大小: $((archive_total_size / 1024 / 1024))MB"
}

#-----------------------------------------------------------------------------
# 函数: 自动轮转检查
# 参数: $1 - 日志文件路径
# 返回: 0-已轮转, 1-不需要轮转
#-----------------------------------------------------------------------------
function log_rotation_check_and_rotate() {
    local log_file="$1"
    
    local strategy="${_LOG_ROTATION_CONFIG[${log_file}_strategy]:-size}"
    
    case "$strategy" in
        size)
            if _should_rotate_by_size "$log_file"; then
                _log_rotation_rotate "$log_file"
                return 0
            fi
            ;;
        time)
            # 时间策略需要外部定时任务触发
            _log_rotation_rotate "$log_file"
            return 0
            ;;
    esac
    
    return 1
}

#-----------------------------------------------------------------------------
# 测试函数
#-----------------------------------------------------------------------------
function _log_rotation_test() {
    echo "=== 日志轮转增强模块测试 ==="
    
    local test_log="/tmp/test_rotation.log"
    
    echo ""
    echo "1. 初始化轮转配置:"
    _log_rotation_init "$test_log" "size" "1K" 3 "true"
    echo "  ✓ 初始化成功"
    
    echo ""
    echo "2. 创建测试日志文件(2KB):"
    dd if=/dev/zero of="$test_log" bs=1024 count=2 2>/dev/null
    echo "  ✓ 文件创建成功"
    
    echo ""
    echo "3. 查看统计信息:"
    _log_rotation_stats "$test_log" | sed 's/^/  /'
    
    echo ""
    echo "4. 检查是否需要轮转:"
    if _should_rotate_by_size "$test_log"; then
        echo "  ✓ 需要轮转"
    else
        echo "  - 不需要轮转"
    fi
    
    echo ""
    echo "5. 执行轮转:"
    _log_rotation_rotate "$test_log"
    echo "  ✓ 轮转完成"
    sleep 1
    
    echo ""
    echo "6. 再次查看统计信息:"
    _log_rotation_stats "$test_log" | sed 's/^/  /'
    
    echo ""
    echo "7. 清理测试文件:"
    rm -f "$test_log"
    rm -rf "$(dirname "$test_log")/archive"
    echo "  ✓ 清理完成"
}

# 如果直接执行此脚本,运行测试
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    _log_rotation_test
fi

